【SFA官方翻译】通过 Zuul 进行动态路由
通过 Zuul 进行动态路由使用 REST API 和没有 Spring Config 的 Spring Boot 子域子路径路由器
原文链接:https://dzone.com/articles/dynamic-routing-through-zuul-with-rest-api-spring
作者:Vikas Anand
译者:umbrellage
问题陈述
有一个要求,把所有的流量都转到 .adomain.com
到 adomain.com/*
。例如: a.adomain.com/**/*
应该转发到 adomain.com/a/**/*
。此外,代理网关应该一直处于运行状态,并且需要在网关中注册新的子域,而不需要任何停机时间。这是一个非常具体的问题,需要一些研究来达到同样的目的。
代理的研究
我们尝试了几个方面,包括节点 http-proxy,Java throo 库(内部使用Zuul)和 Zuul 本身。 所有示例都基于来自用于代理创建的属性文件的预取配置,除了先前的 Zuul 示例,其中讨论了使用 Spring 可刷新云配置和 RabbitMQ 的可刷新配置。 需要运行时代理创建器服务,优选地暴露 POST API 以获取一些参数并在运行时注册新代理。甚至还需要删除 API 来摆脱不再使用的代理。作为 Java 开发人员,自然选择是 Zuul。
先决条件
需要了解 Java、api、代理、Apache、网关概念和 Spring Boot,才能完全理解本文提出的概念。
解决方案
通过 Netflix 提供的 Zuul,现在添加了Spring Cloud分布 ,关于如何使用 Eureka、Zuul 服务器、Spring Cloud 服务器、RabbitMQ 和 Git,有一个很好的例子。 尽管整个示例非常棒,并且提供了很大的灵活性,但它涉及到许多框架和许多需要完成的设置。 我不能使用它,因为我想创建一个非常简单的解决方案来解决我们的问题,考虑到不成为 HA 的权衡。
架构如下:
编码
要使应用程序正常工作,还需要做几件事:
接口
在 POST 中使用参数来创建路由。
Zuul-Related变化
Pom 更改:它包含执行器,zuul 和与 spring-cloud 相关的依赖项。
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>gateway-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.7.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- Dependencies -->
<spring-cloud.version>Camden.SR7</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.application.SpringBootWebApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Spring Boot 启动类:
package com.mettl.gatewayservice.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableAutoConfiguration(exclude = { RabbitAutoConfiguration.class })
@EnableZuulProxy
@ComponentScan("com.example.gatewayservice")
public class SpringBootWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootWebApplication.class, args);
}
}
application.yml
server:
port: ${appPort:80}
info.app.version: @project.version@
# Actuator endpoint path (/admin/info, /admin/health, ...)
server.servlet-path: /
management.context-path: /admin
# ribbon.eureka.enabled: false
zuul:
ignoredPatterns: /**/admin/**, /proxyurl
routes:
zuulDemo1:
path: /**
url: http://localhost:8000/
# stripPrefix set to true if context path is set to /
stripPrefix: true
有两个部分:
1.代理路由的注册
这部分是通过自动连接两个依赖项来创建服务类来完成的,如下所示:
@Service
public class ZuulDynamicRoutingService {
private static final String HTTP = "http://";
private final ZuulProperties zuulProperties;
private final ZuulHandlerMapping zuulHandlerMapping;
......
通过以下代码添加新路由。
可以通过遵循任何标准方式来创建uuid。唯一的问题是它应该是一个唯一的密钥,因为它将被用于所使用的地图中 zuulProperties.getRoutes()
。
String uuid = GenerateUID.getUID();
if (StringUtils.isEmpty(dynamicRouteRequest.getSubpath())) {
dynamicRouteRequest.setSubpath("");
}
String url = "http://" + dynamicRouteRequest.getHost() + ":" + dynamicRouteRequest.getPort() + dynamicRouteRequest.getSubpath();
zuulProperties.getRoutes().put(uuid, new ZuulRoute(uuid, "/" + uuid + "/**", null, url, true, false, new HashSet<>()));
zuulHandlerMapping.setDirty(true);
此服务注入控制器以接收发布请求并将详细信息转发到服务类以创建代理。
可以使用 URL / admin / routes 检查它。
2.使用子域到子路径转换将HTTP请求转发到目标
这需要一个扩展 ZuulFilter 的 PreFilter 类:
@Component
public class PreFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(PreFilter.class);
private UrlPathHelper urlPathHelper = new UrlPathHelper();
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String requestURL = ctx.getRequest().getRequestURL().toString();
//Here we only require to filter those URLs which contains "proxyurl" and "/admin/".
return !(requestURL.contains("proxyurl") || requestURL.contains("/admin/"));
}
//The actual part where the subdomain to subpath conversion happens is as follows:
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String remoteHost = ctx.getRequest().getRemoteHost();
String requestURL = ctx.getRequest().getRequestURL().toString();
if (!requestURL.contains("proxyurl")) {
log.info("remoteHost {} requestURL {}", new Object[]{remoteHost, requestURL});
String originatingRequestUri = this.urlPathHelper.getOriginatingRequestUri(ctx.getRequest());
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
log.info("URI {} original URI {}", new Object[]{requestURI, originatingRequestUri});
String protocol = requestURL.substring(0, requestURL.indexOf("//") + 2);
String urlWithoutProtocol = requestURL.substring(requestURL.indexOf("//") + 2);
String[] split = urlWithoutProtocol.substring(0, urlWithoutProtocol.indexOf("/")).split("\\.");
String subPath = split[0];
final String newURL = protocol + "." + split[1] + "." + split[2];
//Here the main thing is to create a HttpServletRequestWrapper and override the request coming from the actual request
HttpServletRequestWrapper httpServletRequestWrapper = new HttpServletRequestWrapper(ctx.getRequest()) {
public String getRequestURI() {
if (requestURI != null && !requestURI.equals("/")) {
if (!StringUtils.isEmpty(subPath)) {
return "/" + subPath + requestURI;
} else {
return requestURI;
}
}
if (!StringUtils.isEmpty(subPath)) {
return "/" + subPath;
} else {
return "/";
}
}
public StringBuffer getRequestURL() {
return new StringBuffer(newURL);
}
};
ctx.setRequest(httpServletRequestWrapper);
HttpServletRequest request = ctx.getRequest();
log.info("PreFilter: " + String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
}
return null;
}
}
现在,您可以通过使用 mvn springboot:run
或 添加缺少的部分来运行应用程序 java-jar gateway-service.jar
。
现在,使用 Postman 提交请求,如下所示。
去做你自己的事
此会话中缺少 Apache 部分。添加配置以将来自 * .adomain.com 的所有请求转发到托管 Zuul 服务器的 IP,并在启动 Zuul 服务器时配置端口。
在创建代理路由以实际查看正在发生的情况时需要启动请求的应用程序。
概要
我们学会了
通过公开 API 在运行时创建代理路由
更改 PreFilter(ZuulFilter 的子类)以预处理 HTTP 请求
添加 Apache 配置以将请求转发到 Zuul Gateway
招人:数心,造化心数奇;用心等你...
上一篇:【SFA官方翻译】使用 Docker 进行 Spring Boot 开发
点击阅读原文查看更多